#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/wait.h>

#include "sinetd.h"

int unixProgramResult( pid_t programPid, const char* programName ){
	
	int wstatus;
	pid_t waitres;
	
	//wait while programs works
	waitres = waitpid( programPid, &wstatus, 0 );
	if( waitres < 0 ) {
		perror( "waitpid" );
		return -2;
	}
	
	//print result
	if( WIFSIGNALED(wstatus) ) {
		printf( "%s was killed by signal %d\n", programName, WTERMSIG(wstatus) );
		return -1;
	}
	
	if( WIFEXITED(wstatus) ){
		if( WEXITSTATUS(wstatus) ){
			printf( "%s return code %d\n", programName, WEXITSTATUS(wstatus) );
			return -1;
		}
		return 0;
	} 
	
	printf( "FATAL: waitpid return unknown status.\n" );
	return -2;
}


static pid_t startUnixProgram( struct instance* this ){
	
	int res1;
	int res2;
	int res3;
	pid_t forkRes;
	
	//after fork main thread return 
	//and child be works in this function
	forkRes = fork();
	if( forkRes ){
		close( this->clientSocket );
		if( forkRes < 0 ) perror( "startUnixProgram fork" );
		return forkRes;
	}
	
	//close stdio
	res1 = close( fileno(stdin) );
	res2 = close( fileno(stdout) );
	res3 = close( fileno(stderr) );
	if( res1 < 0 || res2 < 0 || res3 < 0 ) exit( 60 );
	
	//replace stdio with socket
	res1 = dup2( this->clientSocket, 0 );
	res2 = dup2( this->clientSocket, 1 );
	res3 = dup2( this->clientSocket, 2 );
	if( res1 < 0 || res2 < 0 || res3 < 0 ) exit( 63 );
	
	//close original socket fd
	close( this->clientSocket );
	
	//exec
	execve( this->argv[0], this->argv, this->envp );
	exit( 66 );
}


static int launchSocketHandler( struct instance* this ){
	
	pid_t handlerPid;
	
	//run programm
	handlerPid = startUnixProgram( this );
	if( handlerPid < 0 ) return -1;
	
	//check status
	unixProgramResult( handlerPid, "Handler" );
	
	return 0;
}

static int openAndListenPort( struct instance* this ){
	
	const int reuseAddrEnable = 1;
	
	int res;
	int serverSocket;
	struct sockaddr_in addr;
	
	//TODO: SIGPIPE
	
	//socket
	serverSocket = socket( AF_INET, SOCK_STREAM, 0 );
	if( serverSocket < 0 ){
		perror("socket");
		return -1;
	}
	
	//set SO_REUSEADDR option for server socket
	res =  setsockopt( serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddrEnable, sizeof(reuseAddrEnable) );
	if( res ){
		perror( "server sock set SO_REUSEADDR" );
		goto l_closeServer;
	}
	
	//set close-on-exec flag on server socket
	res = fcntl( serverSocket, F_SETFD, FD_CLOEXEC );
	if( res ){
		perror( "set close-on-exec flag on server socket" );
		goto l_closeServer;
	}
	
	addr.sin_family = AF_INET;
	addr.sin_port = htons( this->portNo );
	addr.sin_addr.s_addr = htonl( INADDR_ANY );
	
	//bind
	res = bind( serverSocket, (struct sockaddr *)&addr, sizeof(addr) );
	if( res ){
		perror( "bind" );
		goto l_closeServer;
	}
	
	//listen
	res = listen( serverSocket, 10 );
	if( res ){
		perror( "listen" );
		goto l_closeServer;
	}
	
	printf( "Listening %i TCP...\n", this->portNo );
	
	do{
		//accept
		this->clientSocket = accept( serverSocket, NULL, NULL );
		
		if( this->clientSocket < 0 ){
			perror( "accept" );
			goto l_closeServer;
		}
		
		//handle request
		res = launchSocketHandler( this );
		
		//close client socket
		close( this->clientSocket );
		
		//accept next
	}while( res > -2 );
	
	
l_closeServer:
	close( serverSocket );
	return res;
}

int main( int argc, char* argv[], char* envp[] ){
	
	int i;
	int res;
	
	struct instance this = {
		.envp = envp,
		.portNo = 0
	};
	
	//parse arguments
	for( i = 1; i < argc; i++ ){
		
		//port
		if( !strcmp(argv[i], "-p") ){
			i++;
			if( i == argc || this.portNo ) goto l_help;
			res = sscanf( argv[i], "%hu", &this.portNo );
			if( res != 1 ) goto l_help;
			if( !this.portNo || this.portNo > 65535 ){
				printf( "TCP port must be in range 1-65535.\n" );
				return 2;
			}
		
		//options end marker
		}else if( !strcmp(argv[i], "--" ) ){
			i++;
			break;
		}
	}
	
	//check args exists
	if( i == argc ) goto l_help;
	
	//set argv for execve
	this.argv = &argv[i];
	
	//argv terminator check
	if( argv[argc] ){
		printf( "FATAL: argv[argc] != NULL\n" );
		return 1;
	}
	
	//check port is set
	if( !this.portNo ) goto l_help;
	
	//listen port
	res = openAndListenPort( &this );
	if( res ) return 1;
	
	return 0;
	
l_help:
	printf( 
		"Usage:\n"
		"  %s -p <port> -- <exec> [exec args]\n\n"
		"  <port>        TCP port that server will listen.\n"
		"  <exec>        Path to program which will be run.\n"
		"  [exec args]   Parameters that will be passed to the program."
		"\n"
		, argv[0]
	);
	
	return 2;
}
